using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Xml.Serialization;

namespace AdaNNTP
{
	public class Segment : IComparable<Segment>
	{
		public int Size {
			get;
			set;
		}
		
		public int Number {
			get;
			set;
		}
		
		public string MessageID {
			get;
			set;
		}

        private bool isCompleted;
		public bool Completed {
			get
			{
				return isCompleted;	
			}
			set
			{
				if(value == true)
				{
                    if (OnSegmentDownloadComplete != null)
						OnSegmentDownloadComplete();
				}
				isCompleted = value;
			}
		}
		
		public string Contents {
			get;
			set;
		}
		
		public bool Active {
			get;
			set;
		}

        public string Group { 
            get; 
            set; 
        }
		
		public delegate void OnSegmentDownloadCompleteDelegate();
		public event OnSegmentDownloadCompleteDelegate OnSegmentDownloadComplete;

        public int CompareTo(Segment other)
        {
            if (other == null)
                return 1;

            if (this.Number < other.Number)
                return 1;
            else
                return 0;
        }
    }
	
	public class File
	{
		public string Poster {
			get;
			set;
		}
		
		public string Subject {
			get;
			set;
		}
		
		public DateTime Date {
			get;
			set;
		}
		
		public string Contents {
			get;
			set;
		}

        private bool isCompleted;
        public bool Completed
        {
            get
            {
                return isCompleted;
            }
            set
            {
                if (value == true)
                {
                    if (OnFileDownloadComplete != null)
                        OnFileDownloadComplete();
                }
                isCompleted = value;
            }
        }
		
		public List<string> Groups = new List<string>();
		
		//We may need to change this at a later point to a long, for 64bit filesystems.
		//I have yet to see an individual file get anywhere near the limit, and I suspect NTTP servers are using
		//int internally.
		public int Size {
			get;
			set;
		}
		
		public string Filename {
			get;
			set;
		}
		
		public List<Segment> Segments = new List<Segment>();
		
		public double GetProgress()
		{
			var completedCount = (from Segment completedSegment in Segments
									where completedSegment.Completed == true
								  select completedSegment).Count();
			
			double progress = (completedCount / Segments.Count);
			
			if(progress == 1.0f)
				Completed = true;
			
			return progress;
		}
		
		public delegate void OnFileDownloadCompleteDelegate();
		public event OnFileDownloadCompleteDelegate OnFileDownloadComplete;
	}

	public class Task
	{
		public List<File> Files = new List<File>();
		
		public string Title {
			get;
			set;
		}
		
		public string Owner {
			get;
			set;
		}
		
		public double GetProgress()
		{
			var completedFileCount = (from File file in Files
									where file.GetProgress() == 1.0f
									select file).Count();
			
			double progress = (completedFileCount / Files.Count);
			
			if(progress == 1.0f)
                if (OnTaskDownloadComplete != null)
				    OnTaskDownloadComplete();
			
			return progress;
		}
		
		public List<string> Passwords = new List<string>();
		
		public void DecodeNZB(System.IO.Stream nzbStream)
		{
			XmlDocument nzbDocument = new XmlDocument();
			nzbDocument.XmlResolver = null; //We do this to avoid some funky namespace lookup caused by the NZB format.
			nzbDocument.Load(nzbStream);

			//Unless we set this up, XPath won't see the XML elements.
			XmlNamespaceManager nzbNamespaceManager = new XmlNamespaceManager(nzbDocument.NameTable);
			nzbNamespaceManager.AddNamespace("newzBin", "http://www.newzbin.com/DTD/2003/nzb");
			
			
			XmlNodeList headNodeList, fileNodeList;

			//Select the header nodes, title, password, etc.
			headNodeList = nzbDocument.SelectNodes("/newzBin:nzb/newzBin:head", nzbNamespaceManager);

			foreach(XmlNode currentNode in headNodeList)
			{
				foreach (XmlAttribute currentAttribute in currentNode.Attributes)
				{
					switch(currentAttribute.Name)
					{
						case "title":
							Title = currentNode.InnerText;
							break;
						case "password":
							Passwords.Add(currentNode.InnerText);
							break;
						default:
                //          Debug.WriteLine("Unexpected NZB Header attribute: " + currentAttribute.Name);
							break;
					}
				}
			}

			//Select the file list.
			fileNodeList = nzbDocument.SelectNodes("/newzBin:nzb/newzBin:file", nzbNamespaceManager);

			foreach (XmlNode currentNode in fileNodeList)
			{
				string[] groupNames = (from XmlNode groupNode in currentNode.SelectNodes("/newzBin:nzb/newzBin:file/newzBin:groups/newzBin:group", nzbNamespaceManager)
									  select groupNode.InnerText).ToArray();

				File newFile = new File() { 
					Subject = currentNode.Attributes["subject"].InnerText, 
					Date = UnixTimeStampToDateTime(Convert.ToDouble(currentNode.Attributes["date"].InnerText)), 
					Poster = currentNode.Attributes["poster"].InnerText,
                    Groups = new List<string>(groupNames)
				};
				
				//newFile.Groups.AddRange(groupNames);


				XmlNodeList segmentNodeList = currentNode.SelectNodes("/newzBin:nzb/newzBin:file/newzBin:segments/newzBin:segment", nzbNamespaceManager);

				foreach (XmlNode segmentNode in segmentNodeList)
				{
					newFile.Segments.Add(new Segment(){ 
						Size = Convert.ToInt32(segmentNode.Attributes["bytes"].InnerText), 
						Number = Convert.ToInt32(segmentNode.Attributes["number"].InnerText), 
						MessageID = segmentNode.InnerText,
                        Group = groupNames[0]
					});
				}
				Files.Add(newFile);
			}
	
		}
		
		private static string ParseSubject(string p)
		{
			string[] subjectTokens = p.Split('"');

			return subjectTokens[1]; //We're expecting the middle token, the rest of it is irrelevant to us.
		}

		private static DateTime UnixTimeStampToDateTime(double unixTimeStamp)
		{
			// Unix timestamp is seconds past epoch
			System.DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0);
			dtDateTime = dtDateTime.AddSeconds(unixTimeStamp).ToLocalTime();
			return dtDateTime;
		}
		
		public delegate void OnTaskDownloadCompleteDelegate();
		public event OnTaskDownloadCompleteDelegate OnTaskDownloadComplete;
	}

	[XmlRoot("nntpServer")]
	public class NNTPServer : IComparable<NNTPServer>
	{
		[XmlElement("serverAddress")]
		public string serverAddress;
		[XmlElement("serverPort")]
		public int serverPort;
		[XmlElement("useSSL")]
		public bool UseSSL;
		[XmlElement("useLogon")]
		public bool UseLogon;
		[XmlElement("username")]
		public string Username;
		[XmlElement("password")]
		public string Password;
		[XmlElement("maxConnections")]
		public int MaxConnectionCount;
		[XmlIgnore]
		public int CurrentConnectionCount;
		[XmlElement("useGroups")]
		public bool NeedsGroup;

		public int CompareTo(NNTPServer other)
		{
			if (other == null)
				return 1;

			double otherLoad = other.CurrentConnectionCount / other.MaxConnectionCount;
			double myLoad = this.CurrentConnectionCount / this.MaxConnectionCount;
			if (myLoad < otherLoad)
				return 1;
			else
				return 0;
		}
	}
}